/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include "qDataEditor.h"

#include "object.h"

#include <QTableWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include <QItemDelegate>
#include <QComboBox>
#include <QHeaderView>

#include "propertyInspectorDelegate.h"
#include <assert.h>
#include <iostream>

// error message

#include <stdio.h>
#include "../dataControl.h"

#include <string.h>
#include <stdlib.h>

#include <iostream>
#include <fstream>

extern PropertyInspectorDelegate * createDelegate(Controller *controller,  QWidget *parent);


QDataEditor::QDataEditor(Controller *controller, QWidget *parent):QWidget(parent) {
    this->controller = controller;
    assert(controller);
    this->controller->addDataListener(this);

    table = new QTableView();
    model = new QStandardItemModel();
    table->setModel(model);

    QWidget *box = new QWidget(this);

    QHBoxLayout * hlayout = new QHBoxLayout();
    //    propertyBox = new QLineEdit(box);
    typesCombo = new QComboBox(box);
    //typesCombo->addItem("NodeTable");
    //typesCombo->addItem("EdgeTable");
    //    typesCombo->addItem("colormap");
    //    hlayout->addWidget(propertyBox);
    connect(typesCombo, SIGNAL( currentIndexChanged(const QString &) ), SLOT( comboChanged() ));
    connect(typesCombo, SIGNAL(currentIndexChanged ( int )), SLOT(comboChanged()) );
    hlayout->addWidget(typesCombo);

    btSaved = new QPushButton("Save");
    connect(btSaved, SIGNAL(clicked()),this, SLOT(saveToFile()));
    hlayout->addWidget(btSaved);
    box->setLayout(hlayout);


    QWidget *box2 = new QWidget(this);
    QHBoxLayout * hlayout2 = new QHBoxLayout();
    fileBox = new QLineEdit(box2);
    hlayout2->addWidget(fileBox);
    QPushButton *btReload = new QPushButton("Load File");
    connect(btReload, SIGNAL(clicked()),this, SLOT(loadFileClicked()));
    //connect(propertyBox, SIGNAL(returnPressed()),this, SLOT(reloadFile()));
    hlayout2->addWidget(btReload);
    box2->setLayout(hlayout2);


    QVBoxLayout *layout = new QVBoxLayout();
    layout->addWidget(box);
    layout->addWidget(box2);
    layout->addWidget(table);
    setLayout(layout);


    if(this->controller->sources.size()<=0){
        this->iColNum = 3;
        this->iRowNum = 1; //two header lines;

        //setDummyTable();
        this->resizeAllColumn();
    }
    else{
        // set table from file
        typesCombo->currentText() = "NodeTable";
        //reloadFile();
    }

    strCurrentKey = "";

}

QDataEditor::~QDataEditor(){

}




//void QDataEditor::updateDelegate(string name){

//}

void QDataEditor::saveToMappings(string strMapKey, QStandardItemModel *newModel){
    this->mapModels[strMapKey] = newModel;
    this->mapFiles[strMapKey] = this->strFile;
    this->mapColNum[strMapKey] = this->iColNum;
    this->mapRowNum[strMapKey] = this->iRowNum;
    this->mapChanged[strMapKey] = this->ifCellChanged;

    strCurrentKey = strMapKey;


    // save to combo
    typesCombo->addItem(   QString(strCurrentKey.c_str())  );
    mapComboIdx[strCurrentKey] = typesCombo->count()-1;
}

void QDataEditor::loadMappings(string strMapKey){
    this->model = this->mapModels[strMapKey];
    this->strFile = this->mapFiles[strMapKey];
    this->iColNum = this->mapColNum[strMapKey];
    this->iRowNum = this->mapRowNum[strMapKey];
    this->ifCellChanged = this->mapChanged[strMapKey];

    this->strCurrentKey = strMapKey;
    table->setModel(model);


    // update file text box
    fileBox->setText( QString(strFile.c_str() ) );

}

void QDataEditor::dataAdded(const char * file, string event){
    // update table here.

    string strCommand = "";

    bool ifEventOk = false;


    if(event=="importTable"){
        ifEventOk = true;
        strCommand = "Node";
    }
    else if(event=="importEdgeTable"){
        ifEventOk = true;
        strCommand = "Edge";

    }

    else if(event=="importDirectedEdgeTable"){
        ifEventOk = true;
        strCommand = "DirectedEdge";
    }
    else if(event=="importFloat"){
        ifEventOk = true;
        strCommand = "NodeFloat";
    }
    else if(event=="importImplicitEdgeFloat"){
        ifEventOk = true;
        strCommand = "ImplicitEdgeFloat";
    }
    else if(event=="importEdgeFloat"){
        ifEventOk = true;
        strCommand = "EdgeFloat";
    }


    if(ifEventOk){
        bool ifSuccess = false;
        QStandardItemModel *newModel = loadTableFile(file,ifSuccess);

        if(ifSuccess){
            this->saveToMappings(strCommand, newModel); // save to combo too
    //            typesCombo->addItem(   QString(strCommand.c_str())  );
    //            mapComboIdx[strCommand] = typesCombo->count()-1;

            model = newModel;
            table->setModel(model);
            resizeAllColumn();
            updateComboIndex();
        }
    }
}

void QDataEditor::setDummyTable(){

    delegate = createDelegate(controller, this);

    // values for rows;
    for(int y=0; y<iRowNum; y++){

        string strRow = "data#";
        char buf[20];
        sprintf(buf,"%d",y);
        string strRowNum (buf);
        strRow.append(strRowNum);

        for(int x=0; x<iColNum; x++){

            string strProp = "p";
            char buf[20];
            sprintf(buf,"%d",x);
            string strCol (buf);
            strProp.append(strCol);


            string strCell = strRow;
            strCell.append("_");
            strCell.append(strProp);
            QStandardItem *item1 = new QStandardItem(QString(strCell.c_str()));
            model->setItem(y, x, item1);
        }
    }

    // set header lines
    for(int x=0; x<iColNum; x++){
        string strPropName = "Property_";
        char buf[20];
        sprintf(buf,"%d",x);
        string strBuf (buf);
        strPropName.append(strBuf);
        model->setHeaderData(x, Qt::Horizontal, QString(strPropName.c_str()));
    }

    table->setItemDelegate(delegate);
    table->setEditTriggers(QAbstractItemView::AllEditTriggers);
    //table->setColumnHidden(2, false); //true
    //table->setColumnHidden(3, false); //true
    table->resizeColumnToContents(0);
    //table->resizeColumnToContents(1);
    table->horizontalHeader()->setStretchLastSection(true);

    connect(model, SIGNAL(itemChanged(QStandardItem *)), SLOT(cellChanged(QStandardItem *)));
    this->ifCellChanged = false;

}

void QDataEditor::resizeAllColumn(){
    for(int x=0; x<this->iColNum; x++){
        table->resizeColumnToContents(x);
    }


}
void QDataEditor::loadFileClicked(){
    string strFile = fileBox->text().toStdString();

    bool ifSuccess = false;
    QStandardItemModel *newModel = loadTableFile(strFile.c_str(),ifSuccess);
    if(ifSuccess){
        string strNewComboName = "FileTable_";
        int iComboItemNum = typesCombo->count();
        iComboItemNum++;
        char buf[20];
        sprintf(buf,"%d",iComboItemNum);
        string strBuf (buf);
        strNewComboName.append(strBuf);

        this->saveToMappings(strNewComboName, newModel);
        // save to combo
//        typesCombo->addItem(   QString(strNewComboName.c_str())  );
//        mapComboIdx[strNewComboName] = typesCombo->count()-1;

        model = newModel;
        table->setModel(model);
        resizeAllColumn();
        updateComboIndex();
    }
    else{
        // this file do not exist.
    }


}

void QDataEditor::comboChanged(){
    string strNodeOrTable = typesCombo->currentText().toStdString();

    if(this->mapModels.find(strNodeOrTable)!=mapModels.end()){
        // load a model of a previous loaded file.

        // save back
        this->mapChanged[this->strCurrentKey] = this->ifCellChanged;

        // load model
        this->loadMappings(strNodeOrTable);

        resizeAllColumn();

        updateSaveButton();
    }
    else{
//        // load from file again
//        string strFile = "";
//        if(this->mapFiles.find(strNodeOrTable)!=mapFiles.end()){
//            strFile = mapFiles[strNodeOrTable];

//            bool ifSuccess =false;
//            QStandardItemModel *newModel = loadTableFile(strFile.c_str(),ifSuccess);
//            if(ifSuccess){
//                this->saveToMappings(strNodeOrTable, newModel);

//                this->loadMappings(strNodeOrTable);

//                resizeAllColumn();

//                updateSaveButton();
//            }
//        }
    }

}

// ref DataControl::importTable
QStandardItemModel *QDataEditor::loadTableFile(const char *file, bool &ifSuccess){
    QStandardItemModel *newmodel = new QStandardItemModel();

    ifstream inp(file, ifstream::in);
    if(!inp.is_open()) {
        string strRetryFile = "";
        bool retry = controller->retryOpenFile(file,strRetryFile);
        if(!retry){
            cout<<"Cannot open data table "<<file<<endl;//%s\n", file);
            ifSuccess = false;
            return newmodel;
        }
        inp.open(strRetryFile.c_str(), ifstream::in);


    }

    char delims[] = "\t\n\r";
    string line;


    int iColCnt = 0;
    int iRowCnt = 0;
    vector<string> vecTypes;
    vector<string> vecNames;
    //vector<Header> headers;


    // PROP TYPE line ============================================ 1
    getline(inp,line); //FLOAT	FLOAT	FLOAT	INT	INT
    if(inp.eof()) {
        cout<<"Metaheader not found"<<endl;//\n", file);
        inp.close();
        ifSuccess = false;
        return newmodel;
    }
    char *str = (char*) line.c_str();
    char *tok = strtok(str, delims);
    while(tok) {
        //Header h;
        string strTOK = string(tok);
        QStandardItem *item1 = new QStandardItem(QString(strTOK.c_str()));
        newmodel->setItem(iRowCnt, iColCnt, item1);

        vecTypes.push_back(strTOK);
        iColCnt++;
        tok = strtok(NULL, delims);
    }
    iRowCnt++;

    // PROP NAME line ============================================ 2
    getline(inp,line); //Betweenness	Degree	EigenCentralityEigen	NodeId	Parent
    if(inp.eof()) {
        cout<<"Header not found"<<endl;//\n", file);
        inp.close();
        ifSuccess = false;
        return newmodel;
    }
    str = (char*) line.c_str();
    tok = strtok(str, delims);
    iColCnt = 0;
    int k = 0;
    while(tok) {
        if(k>=(int) vecTypes.size()) {
            cout<<"Header column count does not match meta-header count"<<endl;//\n");
            inp.close();
            return newmodel;
        }
        cout<<"Reading header "<<k<<" "<<tok<<endl;//%s\n", k, tok);

        string strPropName = string(tok);
        QStandardItem *item1 = new QStandardItem(QString(strPropName.c_str()));
        newmodel->setItem(iRowCnt, iColCnt, item1);

        vecNames.push_back(strPropName);
        iColCnt++;
        k++;
        tok = strtok(NULL, delims);
    }
    if(k<(int) vecTypes.size()) {
        cout<<"Header column count does not match meta-header count"<<endl;//\n");
        return newmodel;
    }
    iRowCnt++;
    iColCnt = 0;

    //set table header as the property name
    for(int x=0; x<vecNames.size(); x++){
        string strPropName = vecNames[x];
        newmodel->setHeaderData(x, Qt::Horizontal, QString(strPropName.c_str()));
    }


    // data lines ============================================ 3-n
    while(!inp.eof()) {
        getline(inp,line);
        char *str = (char*) line.c_str();
        if(strlen(str)==0) continue;
        //print("Processing line [%s]\n", line.c_str());
        char *tok = strtok(str, delims);
        int k = 0;
        vector<string> properties;
        while(tok) {
            if(k>=(int) vecTypes.size()) {
                cout<<"Header column count does not match meta-header count"<<endl;//\n");
                inp.close();
                ifSuccess = false;
                return newmodel;
            }
            string strTOK = string(tok);
            QStandardItem *item1 = new QStandardItem(QString(strTOK.c_str()));
            newmodel->setItem(iRowCnt, k, item1);


            tok = strtok(NULL, delims);
            k++;

        }

        iRowCnt++;

    }
    inp.close();

    connect(newmodel, SIGNAL(itemChanged(QStandardItem *)), SLOT(cellChanged(QStandardItem *)));

    this->strFile = string(file);
    this->iColNum = vecTypes.size();
    this->iRowNum = iRowCnt;
    this->ifCellChanged = false;


    ifSuccess = true;
    return newmodel;

}

void QDataEditor::cellChanged(QStandardItem *item){
    int row = item->index().row();
    int col = item->index().column();
    //QModelIndex parent = item->index().parent();

    string strNewValue = item->data(Qt::EditRole).toString().toStdString();

    if(!ifCellChanged){
        this->ifCellChanged = true;
        updateSaveButton();
    }
    else{ // already some changed.

    }
}

// called when button "save" is clicked.
void QDataEditor::saveToFile(){

    if(ifCellChanged){
        // write to file
        bool ifSaved = saveTableToFile();
        if(ifSaved){
            ifCellChanged = false;
            updateSaveButton();
        }
    }

}


void QDataEditor::updateSaveButton(){
    if(ifCellChanged){
        btSaved->setText("**SAVE**");
    }
    else{
        btSaved->setText("Save");
    }
}

void QDataEditor::updateComboIndex(){
    int comboIdx = mapComboIdx[strCurrentKey];
    typesCombo->setCurrentIndex(comboIdx);

}

bool QDataEditor::saveTableToFile(){
    string strFilename = this->strFile;
    //strFilename.append("__test0616.txt");
    ofstream outp(strFilename.c_str());
    if(!outp.is_open()) {
      cout << "Cannot open file " << strFilename << endl;
      return false;
    }

    for(int y=0; y<this->iRowNum; y++){
        string strLine = "";
        for(int x=0; x<this->iColNum; x++){
            QStandardItem *itemProp = model->item(y, x) ;
            string strValue = itemProp->data(Qt::EditRole).toString().toStdString();
            strLine.append(strValue);

            if(x!=iColNum-1){
                strLine.append("\t");
            }
        }
        outp << strLine << endl;
    }
    outp.close();
    return true;

}
